// ==============================================================================
//
// Software Development Kit v 3.0
// xml parser API
//
// Copyright (c) 2008-2009 Gear Pair Games
//
// Copyright and license exceptions noted in source
//
// ==============================================================================
unit xml;

interface

uses
  types;

type
  xmlParamValue = ansiString; // Variant

  structXMLParam = record
    keyName : ansiString;
    asText : ansiString;
    function asInt : int;
    function asFloat : float;
  end;

  CXMLParams = class
    constructor Create( const inText : ansiString );
  protected
    fParams : array of structXMLParam;
    function getParam( const inName : ansiString ) : structXMLParam;
    function getParamI( inIdx : int ) : structXMLParam;
  public
    function count : int;
    property param[ const inName : ansiString ] : structXMLParam read getParam; default;
    property paramI[ inIdx : int ] : structXMLParam read getParamI;
  end;

// CXML
  CXML = class
    constructor Create( const inFileName : ansiString ); overload;
    constructor Create( const inFile : void; inCloseOnEnd : bool ); overload;
    constructor Create( const inText : ansiString; inBeginPos : int ); overload;
    destructor Destroy; override;
  protected
    fNode : array of CXML;
    fTag : ansiString;
    fContent : ansiString;
    fDataLen : int;
    fParams : CXMLParams;
    function GetNode( const inTagName : ansiString ) : CXML;
    function GetNodeI( inIdx : int ) : CXML;
  public
    function count : int;
    property tag : ansiString read fTag;
    property content : ansiString read fContent;
    property dataLen : int read fDataLen;
    property params : CXMLParams read fParams;
    property node[ const inTagName : ansiString ] : CXML read GetNode; default;
    property nodeI[ inIdx : int ] : CXML read GetNodeI;
  end;

implementation

uses
  fileApi,
  utils;

const
  nullParam : structXMLParam = ( keyName : ''; asText : '' );

function TrimQuote( const inText : ansiString ) : ansiString;
begin
  Result := inText;
  if Result[ 1 ] = '"' then
    Delete( Result, 1, 1 );
  if Result[ Length( Result ) ] = '"' then
    Delete( Result, Length( Result ), 1 );
end;

function trimCopy( const inText : ansiString; index, count : int ) : ansiString;
const
  TrimChars : set of AnsiChar = [ #9, #10, #13, #32 ];
var
  i : int;
begin
  for i := index to index + count - 1 do
    if inText[ i ] in TrimChars then
    begin
     Inc( index );
     Dec( count );
    end
    else
     break;
  for i := index + count - 1 downto index do
    if inText[ i ] in TrimChars then
     Dec( count )
    else
     break;
  Result := Copy( inText, index, count );
end;

{ CXMLParams }
constructor CXMLParams.Create( const inText : ansiString );
var
  i : int;
  flag : ( f_begin, f_name, f_value );
  paramIdx : int;
  indexBegin : int;
  readValue : bool;
  textFlag : bool;
begin
  inherited Create;
  flag := f_begin;
  paramIdx := -1;
  indexBegin := 1;
  readValue := False;
  textFlag := False;
  for i := 1 to Length( inText ) do
    case flag of
     f_begin :
     if inText[ i ] <> ' ' then
     begin
     paramIdx := Length( fParams );
     SetLength( fParams, paramIdx + 1 );
     fParams[ paramIdx ].keyName := '';
     fParams[ paramIdx ].asText := '';
     flag := f_name;
     indexBegin := i;
     end;
     f_name :
     if inText[ i ] = '=' then
     begin
     fParams[ paramIdx ].keyName := trimCopy( inText, indexBegin, i - indexBegin );
     flag := f_value;
     indexBegin := i + 1;
     end;
     f_value :
     begin
     if inText[ i ] = '"' then
     textFlag := not textFlag;
     if ( inText[ i ] <> ' ' ) and ( not textFlag ) then
     readValue := true
     else
     if readValue then
     begin
     fParams[ paramIdx ].asText := TrimQuote( trimCopy( inText, indexBegin, i - indexBegin ) );
     flag := f_begin;
     readValue := False;
     paramIdx := -1;
     end
     else
     continue;
     end;
    end;
  if paramIdx <> -1 then
    fParams[ paramIdx ].asText := TrimQuote( trimCopy( inText, indexBegin, Length( inText ) - indexBegin + 1 ) );
end;

function CXMLParams.count : int;
begin
  Result := Length( fParams );
end;

function CXMLParams.getParam( const inName : ansiString ) : structXMLParam;
var
  i : int;
begin
  for i := 0 to count - 1 do
    if fParams[ i ].keyName = inName then
    begin
     Result.keyName := PAnsiChar( fParams[ i ].keyName );
     Result.asText := PAnsiChar( fParams[ i ].asText );
     Exit;
    end;
  Result := nullParam;
end;

function CXMLParams.getParamI( inIdx : int ) : structXMLParam;
begin
  Result.keyName := PAnsiChar( fParams[ inIdx ].keyName );
  Result.asText := PAnsiChar( fParams[ inIdx ].asText );
end;

{ CXML }
constructor CXML.Create( const inFileName : ansiString );
var
  xmlFile : CTextFile;
begin
  inherited Create;
// xmlFile create
  xmlFile := CTextFile.Create;
// xmlFile
  if xmlFile.open( inFileName ) then
    Create( xmlFile, False ); // xml create
// close file
  xmlFile.close;
end;

constructor CXML.Create( const inText : ansiString; inBeginPos : int );
var
  i, j : int;
  flag : ( f_begin, F_TAG, F_PARAMS, F_CONTENT, F_END );
  BeginIndex : int;
  textFlag : bool;
begin
  inherited Create;
  textFlag := False;
  flag := f_begin;
  i := inBeginPos - 1;

  BeginIndex := inBeginPos;
  fContent := '';
  while i <= Length( inText ) do
  begin
    Inc( i );
    case flag of
    // waiting for new tag '<...'
     f_begin :
     if inText[ i ] = '<' then
     begin
     flag := F_TAG;
     BeginIndex := i + 1;
     end;
    // waiting for tag inName '... ' or '.../' or '...>'
     F_TAG :
     begin
     case inText[ i ] of
     '>' :
     flag := F_CONTENT;
     '/' :
     flag := F_END;
     ' ' :
     flag := F_PARAMS;
     '?', '!' :
     begin
     flag := f_begin;
     continue;
     end
     else
     continue;
     end;
     fTag := trimCopy( inText, BeginIndex, i - BeginIndex );
     BeginIndex := i + 1;
     end;
    // parse tag parameters
     F_PARAMS :
     begin
     if inText[ i ] = '"' then
     textFlag := not textFlag;
     if not textFlag then
     begin
     case inText[ i ] of
     '>' :
     flag := F_CONTENT;
     '/' :
     flag := F_END;
     else
     continue;
     end;
     fParams := CXMLParams.Create( trimCopy( inText, BeginIndex, i - BeginIndex ) );
     BeginIndex := i + 1;
     end;
     end;
    // parse tag content
     F_CONTENT :
     begin
     case inText[ i ] of
     '"' :
     textFlag := not textFlag;
     '<' :
     if not textFlag then
     begin
     fContent := fContent + trimCopy( inText, BeginIndex, i - BeginIndex );
              // is new tag or my tag closing?
     for j := i to Length( inText ) do
     if inText[ j ] = '>' then
     begin
     if trimCopy( inText, i + 1, j - i - 1 ) <> '/' + fTag then
     begin
     SetLength( fNode, Length( fNode ) + 1 );
     fNode[ Length( fNode ) - 1 ] := CXML.Create( inText, i - 1 );
     i := i + fNode[ Length( fNode ) - 1 ].dataLen;
     BeginIndex := i + 1;
     end
     else
     flag := F_END;
     break;
     end;
     end
     end;
     end;
    // waiting for close tag
     F_END :
     if inText[ i ] = '>' then
     begin
     fDataLen := i - inBeginPos;
     break;
     end;
    end;
  end;
end;

constructor CXML.Create( const inFile : void; inCloseOnEnd : bool );
begin
  inherited Create;
  self.Create( CTextFile( inFile ).readText, 1 );
  // close
  if inCloseOnEnd then
    CTextFile( inFile ).close;
end;

destructor CXML.Destroy;
var
  i : int;
begin
  for i := 0 to count - 1 do
    nodeI[ i ].Free;
  fParams.Free;
  inherited;
end;

function CXML.count : int;
begin
  Result := Length( fNode );
end;

function CXML.GetNode( const inTagName : ansiString ) : CXML;
var
  i : int;
begin
  for i := 0 to count - 1 do
    if fNode[ i ].tag = inTagName then
    begin
     Result := fNode[ i ];
     Exit;
    end;
  Result := nil;
end;

function CXML.GetNodeI( inIdx : int ) : CXML;
begin
  Result := fNode[ inIdx ];
end;

{ structXMLParam }

function structXMLParam.asFloat : float;
begin
  Result := strFloat( asText );
end;

function structXMLParam.asInt : int;
begin
  Result := strInt( asText );
end;

end.
